package com.cy.common.aspect;

import com.cy.common.annotation.RequiredLog;
import com.cy.common.util.IPUtils;
import com.cy.sys.pojo.SysLog;
import com.cy.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * @Aspect 注解描述的对象为一个切面对象,在切面对象中定义
 * 1)切入点(Pointcut):织入扩展功能的一些连接点的集合
 * 2)通知方法(Advice):封装了扩展逻辑的方法
 */
@Slf4j
@Aspect
@Component
public class SysLogAspect {
    //通过Pointcut定义一个切入点,@annotation方式为定义切入点的一种方式,
    //在这里表示业务对象中由com.cy.pj.common.annotation.RequiredLog注解描述的方法为一些切入点方法
    @Pointcut("@annotation(com.cy.common.annotation.RequiredLog)")
    public void doLog(){}//doLog方法仅仅是@Pointcut注解的一个载体,方法体内不需要写任何内容

    /**
     * @Around 注解描述的方法可以在目标方法执行之前和之后做功能扩展
     * @param jointPoint 封装了目标方法信息的一个对象(连接点对象)
     * @return 目标方法的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jointPoint) throws Throwable{
        try {
            long startTime=System.currentTimeMillis();
            //执行目标方法(result为目标方法的执行结果)
            Object result=jointPoint.proceed();
            long endTime=System.currentTimeMillis();
            long totalTime=endTime-startTime;
            //System.out.println("方法执行的总时长为:"+totalTime);
            log.info("方法执行的总时长为:"+totalTime);
            saveSysLog(jointPoint,totalTime);
            return result;
        }catch(Throwable e){
            //saveErrorLog(...);也可以将错误日志写入到数据库
            logError(jointPoint,e.getMessage());
            throw e;
        }
    }
    //将错误日志进行输出并记录
    private void logError(ProceedingJoinPoint joinPoint,String exceptionMsg) throws JsonProcessingException {
        String targetClassName=joinPoint.getTarget().getClass().getName();
        String methodName=joinPoint.getSignature().getName();
        String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
        log.error("error.msg->{}->{}-{}",targetClassName+"."+methodName,params,exceptionMsg);
    }

    @Autowired
    private SysLogService sysLogService;

    private void saveSysLog(ProceedingJoinPoint point,long totleTime) throws NoSuchMethodException, SecurityException, JsonProcessingException{
        //1.获取日志信息
        //获取目标对象类型
        Class<?> targetClass=point.getTarget().getClass();
        //String className=targetClass.getName();
        //获取目标方法的签名信息
        MethodSignature ms= (MethodSignature)point.getSignature();
        /*
        //获取接口声明的方法
        String methodName=ms.getMethod().getName();
        Class<?>[] parameterTypes=ms.getMethod().getParameterTypes();
        //获取目标对象方法(AOP版本不同,可能获取方法对象方式也不同)
        Method targetMethod=targetClass.getDeclaredMethod(methodName,parameterTypes);
        */
        //获取目标方法?(类中方法的唯一标识是什么:方法名+参数列表)
        Method targetMethod=targetClass.getDeclaredMethod(ms.getName(),ms.getParameterTypes());

        //获取用户名,学完shiro再进行自定义实现,没有就先给固定值
        //String username=ShiroUtils.getPrincipal().getUsername();

        //获取方法参数
        Object[] paramsObj=point.getArgs();
        System.out.println("paramsObj="+paramsObj);
        //将参数转换为字符串
        String params=new ObjectMapper().writeValueAsString(paramsObj);
        //假如目标方法对象上有注解,我们获取注解定义的操作值
        //获取操作名(RequiredLog中operation的值)
        RequiredLog requestLog=targetMethod.getDeclaredAnnotation(RequiredLog.class);
        //获取操作名(RequiredLog中operation的值)
        String operation= requestLog.value();
        //2.封装日志信息
        SysLog log=new SysLog();
        log.setUsername("admin");//登陆的用户//将来这个位置为登录用户名
        log.setIp(IPUtils.getIpAddr());//ip 地址
        if(requestLog!=null){
            log.setOperation(operation);//为目标方法指定的一个名字
        }
        //log.setMethod(className+"."+methodName);//className.methodName()
        log.setMethod(targetClass.getName()+"."+targetMethod.getName());//类全名+方法名
        //log.setParams(Arrays.toString(point.getArgs()));//调用方法时传递实际参数
        log.setParams(new ObjectMapper().writeValueAsString(point.getArgs()));
        log.setTime(totleTime);
        log.setCreatedTime(new Date());
        //3.保存日志信息
        //sysLogService.saveObject(log);
        new Thread(){//1M
            @Override
            public void run(){
                sysLogService.saveObject(log);
            }
        }.start();
    }
}


